﻿// Copyright (C) 2009 Monadsphere <monadshpere@gmail.com>
//
// This file is part of Monadsphere.
//
// Monadsphere is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
// (at your option) any later version.
//
// Monadsphere is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Monadsphere. If not, see <http://www.gnu.org/licenses/>.

using monadsphere.js;
using monadsphere.dom.views;

/// <summary>
/// This namespace implements DOM Level 3Events Specification.
/// See http://www.w3.org/TR/2007/WD-DOM-Level-3-Events-20071221/ for details.
/// 
/// To take advantage of C# delegates, EventListener is defined as a delegate.
/// Further, to distinguish listeners for different types of events, the following
/// extra delegates are declared:
///     CustomEventListener
///     UIEventListener
///     TextEventListener
///     KeyboardEventListener
///     MouseEventListener
///     MouseMultiWheelEventListener
///     MouseWheelEventListener
///     MutationEventListener
///     MutationNameEventListener
///     
/// Also, a new class HtmlEventHandler is provided to wrap any above listeners to take advantage
/// of the extra parameter useCapture in addEventListener and removeEventListener.
/// 
/// For how the event listeners are used, see implementation of
/// monadsphere.dom.html2.HTMLElement and definition of onclick.
/// </summary>
/// 

namespace monadsphere.dom.events {

    public class Event<T>: JSVar where T: EventTarget {

        public const short CAPTURING_PHASE = 1;
        public const short AT_TARGET = 2;
        public const short BUBBLING_PHASE = 3;

        public Event() {
            name = "evt";
        }

        public Event(JSVar src)
            : base(src) {
        }

        public JSString type {
            get {
                return getProperty<JSString>("type");
            }
        }

        public T target {
            get {
                JSBoolean isIE = JSVar.getExistingVariable<JSBoolean>("monadsphere.isIE");
                return JSStmt.TERNARY<T>(isIE, getPropertyInline<JSVar>("srcElement"), getPropertyInline<JSVar>("target"));
            }
        }

        public T currentTarget {
            get {
                return getProperty<T>("currentTarget");
            }
        }

        public JSNumber eventPhase {
            get {
                return getProperty<JSNumber>("eventPhase");
            }
        }

        public JSBoolean bubbles {
            get {
                return getProperty<JSBoolean>("bubbles");
            }
        }

        public JSBoolean cancelable {
            get {
                return getProperty<JSBoolean>("cancelable");
            }
        }

        public JSDate timeStamp {
            get {
                return getProperty<JSDate>("timeStamp");
            }
        }

        public JSString namespaceURI {
            get {
                return getProperty<JSString>("namespaceURI");
            }
        }

        public JSBoolean defaultPrevented {
            get {
                return getProperty<JSBoolean>("defaultPrevented");
            }
        }

        public void stopPropagation() {
            callProcedure("stopPropagation");
        }

        public void preventDefault() {
            callProcedure("preventDefault");
        }

        public void initEvent(JSString eventTypeArg,
                              JSBoolean canBubbleArg,
                              JSBoolean cancelableArg) {
            callProcedure("initEvent", eventTypeArg, canBubbleArg, cancelableArg);
        }

        public void stopImmediatePropagation() {
            callProcedure("stopImmediatePropagation");
        }

        public void initEventNS(JSString namespaceURIArg,
                                JSString eventTypeArg,
                                JSBoolean canBubbleArg,
                                JSBoolean cancelableArg) {
            callProcedure("initEventNS", namespaceURIArg, eventTypeArg, canBubbleArg, cancelableArg);
        }
    }

    public class CustomEvent<T>: Event<T> where T: EventTarget {
        public JSObject detail {
            get {
                return getProperty<JSObject>("detail");
            }
        }

        public void initCustomEventNS(JSString namespaceURIArg,
                                      string typeArg,
                                      JSBoolean canBubbleArg,
                                      JSBoolean cancelableArg,
                                      JSObject detailArg) {
            callProcedure("initCustomEventNS", namespaceURIArg,
                                      typeArg,
                                      canBubbleArg,
                                      cancelableArg,
                                      detailArg);
        }
    }

    /// <summary>
    /// addEventListenerNS and removeEventListenerNS are not defined.
    /// </summary>
    public interface EventTarget: JSAny {
        void addEventListener<T>(string type,
                                     EventListener<T> listener,
                                     bool useCapture) where T: EventTarget;

        void removeEventListener<T>(string type,
                                        EventListener<T> listener,
                                        bool useCapture) where T: EventTarget;

        JSBoolean dispatchEvent<T>(Event<T> evt) where T: EventTarget;
    }

    public delegate void EventListener<T>(Event<T> e)
        where T: EventTarget;

    public delegate void CustomEventListener<T>(CustomEvent<T> e)
        where T: EventTarget;

    public delegate void UIEventListener<T>(UIEvent<T> e)
        where T: EventTarget;

    public delegate void TextEventListener<T>(TextEvent<T> e)
        where T: EventTarget;

    public delegate void KeyboardEventListener<T>(KeyboardEvent<T> e)
        where T: EventTarget;

    public delegate void MouseEventListener<T>(MouseEvent<T> e)
        where T: EventTarget;

    public delegate void MouseMultiWheelEventListener<T>(MouseMultiWheelEvent<T> e)
        where T: EventTarget;

    public delegate void MouseWheelEventListener<T>(MouseWheelEvent<T> e)
        where T: EventTarget;

    public delegate void MutationEventListener<T>(MutationEvent<T> e)
        where T: EventTarget;

    public delegate void MutationNameEventListener<T>(MutationNameEvent<T> e)
        where T: EventTarget;

    public interface DocumentEvent {
        Event<T> createEvent<T>(JSString eventType) where T: EventTarget;
        JSBoolean canDispatch(JSString namespaceURI,
                               JSString type);
    }

    public class UIEvent<T>: Event<T> where T: EventTarget {

        public UIEvent()
            : base() {
        }

        public AbstractView view {
            get {
                return getProperty<AbstractView>("view");
            }
        }

        public JSNumber detail {
            get {
                return getProperty<JSNumber>("detail");
            }
        }

        public void initUIEvent(JSString typeArg,
                                JSBoolean canBubbleArg,
                                JSBoolean cancelableArg,
                                AbstractView viewArg,
                                JSNumber detailArg) {
            callProcedure("initUIEvent", typeArg, canBubbleArg, cancelableArg, viewArg, detailArg);
        }

        public void initUIEventNS(JSString namespaceURIArg,
                                  JSString typeArg,
                                  JSBoolean canBubbleArg,
                                  JSBoolean cancelableArg,
                                  AbstractView viewArg,
                                  JSNumber detailArg) {
            callProcedure("initUIEvent", namespaceURIArg, typeArg, canBubbleArg, cancelableArg, viewArg, detailArg);
        }
    }

    public class TextEvent<T>: UIEvent<T> where T: EventTarget {
        public JSString data {
            get {
                return getProperty<JSString>("data");
            }
        }

        public void initTextEvent(JSString typeArg,
                                  JSBoolean canBubbleArg,
                                  JSBoolean cancelableArg,
                                  AbstractView viewArg,
                                  JSString dataArg) {
            callProcedure("initTextEvent", typeArg, canBubbleArg, cancelableArg, viewArg, dataArg);
        }

        public void initTextEventNS(string namespaceURIArg,
                                    string typeArg,
                                    JSBoolean canBubbleArg,
                                    JSBoolean cancelableArg,
                                    AbstractView viewArg,
                                    string dataArg) {
            callProcedure("initTextEventNS", namespaceURIArg, typeArg, canBubbleArg, cancelableArg, viewArg, dataArg);
        }
    }

    public class KeyboardEvent<T>: UIEvent<T> where T: EventTarget {

        public const int DOM_KEY_LOCATION_STANDARD = 0x00;
        public const int DOM_KEY_LOCATION_LEFT = 0x01;
        public const int DOM_KEY_LOCATION_RIGHT = 0x02;
        public const int DOM_KEY_LOCATION_NUMPAD = 0x03;

        public JSString keyIdentifier {
            get {
                return getProperty<JSNumber>("keyIdentifier");
            }
        }

        public JSNumber keyLocation {
            get {
                return getProperty<JSNumber>("keyLocation");
            }
        }

        public JSBoolean ctrlKey {
            get {
                return getProperty<JSBoolean>("ctrlKey");
            }
        }

        public JSBoolean shiftKey {
            get {
                return getProperty<JSBoolean>("shiftKey");
            }
        }

        public JSBoolean altKey {
            get {
                return getProperty<JSBoolean>("altKey");
            }
        }

        public JSBoolean metaKey {
            get {
                return getProperty<JSBoolean>("metaKey");
            }
        }

        public JSBoolean getModifierState(JSString keyIdentifierArg) {
            return callFunction<JSBoolean>("getModifierState", keyIdentifierArg);
        }

        public void initKeyboardEvent(JSString typeArg,
                                      JSBoolean canBubbleArg,
                                      JSBoolean cancelableArg,
                                      AbstractView viewArg,
                                      JSString keyIdentifierArg,
                                      JSNumber keyLocationArg,
                                      JSString modifiersListArg) {
            callProcedure("initKeyboardEvent", typeArg, canBubbleArg, cancelableArg, viewArg, keyIdentifierArg, keyLocationArg, modifiersListArg);
        }

        public void initKeyboardEventNS(JSString namespaceURIArg,
                                        JSString typeArg,
                                        JSBoolean canBubbleArg,
                                        JSBoolean cancelableArg,
                                        AbstractView viewArg,
                                        JSString keyIdentifierArg,
                                        JSNumber keyLocationArg,
                                        JSString modifiersListArg) {
            callProcedure("initKeyboardEvent", namespaceURIArg, typeArg, canBubbleArg, cancelableArg, viewArg, keyIdentifierArg, keyLocationArg, modifiersListArg);
        }
    }

    public class MouseEvent<T>: UIEvent<T> where T: EventTarget {

        public MouseEvent()
            : base() {
        }
        public JSNumber screenX {
            get {
                return getProperty<JSNumber>("screenX");
            }
        }

        public JSNumber screenY {
            get {
                return getProperty<JSNumber>("screenY");
            }
        }

        public JSNumber clientX {
            get {
                return getProperty<JSNumber>("clientX");
            }
        }

        public JSNumber clientY {
            get {
                return getProperty<JSNumber>("clientY");
            }
        }

        public JSBoolean ctrlKey {
            get {
                return getProperty<JSBoolean>("ctrlKey");
            }
        }

        public JSBoolean shiftKey {
            get {
                return getProperty<JSBoolean>("shiftKey");
            }
        }

        public JSBoolean altKey {
            get {
                return getProperty<JSBoolean>("altKey");
            }
        }

        public JSBoolean metaKey {
            get {
                return getProperty<JSBoolean>("metaKey");
            }
        }

        public JSNumber button {
            get {
                return getProperty<JSNumber>("button");
            }
        }

        public EventTarget relatedTarget {
            get {
                return getProperty<EventTarget>("relatedTarget");
            }
        }

        public void initMouseEvent(JSString typeArg,
                                   JSBoolean canBubbleArg,
                                   JSBoolean cancelableArg,
                                   AbstractView viewArg,
                                   JSNumber detailArg,
                                   JSNumber screenXArg,
                                   JSNumber screenYArg,
                                   JSNumber clientXArg,
                                   JSNumber clientYArg,
                                   JSBoolean ctrlKeyArg,
                                   JSBoolean altKeyArg,
                                   JSBoolean shiftKeyArg,
                                   JSBoolean metaKeyArg,
                                   JSNumber buttonArg,
                                   EventTarget relatedTargetArg) {
            callProcedure("initMouseEvent", typeArg,
                                   canBubbleArg,
                                   cancelableArg,
                                   viewArg,
                                   detailArg,
                                   screenXArg,
                                   screenYArg,
                                   clientXArg,
                                   clientYArg,
                                   ctrlKeyArg,
                                   altKeyArg,
                                   shiftKeyArg,
                                   metaKeyArg,
                                   buttonArg,
                                   relatedTargetArg);
        }

        public JSBoolean getModifierState(JSString keyIdentifierArg) {
            return callFunction<JSBoolean>("getModifierState", keyIdentifierArg);
        }

        public void initMouseEventNS(JSString namespaceURIArg,
                                     JSString typeArg,
                                     JSBoolean canBubbleArg,
                                     JSBoolean cancelableArg,
                                     AbstractView viewArg,
                                     JSNumber detailArg,
                                     JSNumber screenXArg,
                                     JSNumber screenYArg,
                                     JSNumber clientXArg,
                                     JSNumber clientYArg,
                                     JSNumber buttonArg,
                                     EventTarget relatedTargetArg,
                                     JSString modifiersListArg) {
            callProcedure("initMouseEventNS", namespaceURIArg,
                                     typeArg,
                                     canBubbleArg,
                                     cancelableArg,
                                     viewArg,
                                     detailArg,
                                     screenXArg,
                                     screenYArg,
                                     clientXArg,
                                     clientYArg,
                                     buttonArg,
                                     relatedTargetArg,
                                     modifiersListArg);
        }
    }

    public class MouseMultiWheelEvent<T>: MouseEvent<T> where T: EventTarget {
        public JSNumber wheelDeltaX {
            get {
                return getProperty<JSNumber>("wheelDeltaX");
            }
        }

        public JSNumber wheelDeltaY {
            get {
                return getProperty<JSNumber>("wheelDeltaY");
            }
        }

        public JSNumber wheelDeltaZ {
            get {
                return getProperty<JSNumber>("wheelDeltaZ");
            }
        }

        public void initMouseMultiWheelEventNS(JSString namespaceURIArg,
                                               JSString typeArg,
                                               JSBoolean canBubbleArg,
                                               JSBoolean cancelableArg,
                                               AbstractView viewArg,
                                               JSNumber detailArg,
                                               JSNumber screenXArg,
                                               JSNumber screenYArg,
                                               JSNumber clientXArg,
                                               JSNumber clientYArg,
                                               JSNumber buttonArg,
                                               EventTarget relatedTargetArg,
                                               JSString modifiersListArg,
                                               JSNumber wheelDeltaXArg,
                                               JSNumber wheelDeltaYArg,
                                               JSNumber wheelDeltaZArg) {
            callProcedure("initMouseMultiWheelEventNS", namespaceURIArg,
                                               typeArg,
                                               canBubbleArg,
                                               cancelableArg,
                                               viewArg,
                                               detailArg,
                                               screenXArg,
                                               screenYArg,
                                               clientXArg,
                                               clientYArg,
                                               buttonArg,
                                               relatedTargetArg,
                                               modifiersListArg,
                                               wheelDeltaXArg,
                                               wheelDeltaYArg,
                                               wheelDeltaZArg);
        }
    }

    public class MouseWheelEvent<T>: MouseEvent<T> where T: EventTarget {
        public JSNumber wheelDelta {
            get {
                return getProperty<JSNumber>("wheelDelta");
            }
        }

        public void initMouseWheelEventNS(JSString namespaceURIArg,
                                          JSString typeArg,
                                          JSBoolean canBubbleArg,
                                          JSBoolean cancelableArg,
                                          AbstractView viewArg,
                                          JSNumber detailArg,
                                          JSNumber screenXArg,
                                          JSNumber screenYArg,
                                          JSNumber clientXArg,
                                          JSNumber clientYArg,
                                          JSNumber buttonArg,
                                          EventTarget relatedTargetArg,
                                          JSString modifiersListArg,
                                          JSNumber wheelDeltaArg) {
            callProcedure("initMouseWheelEventNS", namespaceURIArg,
                                          typeArg,
                                          canBubbleArg,
                                          cancelableArg,
                                          viewArg,
                                          detailArg,
                                          screenXArg,
                                          screenYArg,
                                          clientXArg,
                                          clientYArg,
                                          buttonArg,
                                          relatedTargetArg,
                                          modifiersListArg,
                                          wheelDeltaArg);
        }
    }

    public class MutationEvent<T>: Event<T> where T: EventTarget {
        // attrChangeType
        public const short MODIFICATION = 1;
        public const short ADDITION = 2;
        public const short REMOVAL = 3;

        public Node relatedNode {
            get {
                return getProperty<Node>("relatedNode");
            }
        }

        public JSString prevValue {
            get {
                return getProperty<JSString>("prevValue");
            }
        }

        public JSString newValue {
            get {
                return getProperty<JSString>("newValue");
            }
        }

        public JSString attrName {
            get {
                return getProperty<JSString>("attrName");
            }
        }

        public JSNumber attrChange {
            get {
                return getProperty<JSNumber>("attrChange");
            }
        }

        public void initMutationEvent(JSString typeArg,
                                      JSBoolean canBubbleArg,
                                      JSBoolean cancelableArg,
                                      Node relatedNodeArg,
                                      JSString prevValueArg,
                                      JSString newValueArg,
                                      JSString attrNameArg,
                                      JSNumber attrChangeArg) {
            callProcedure("initMutationEvent", typeArg,
                                      canBubbleArg,
                                      cancelableArg,
                                      relatedNodeArg,
                                      prevValueArg,
                                      newValueArg,
                                      attrNameArg,
                                      attrChangeArg);
        }

        public void initMutationEventNS(JSString namespaceURIArg,
                                        JSString typeArg,
                                        JSBoolean canBubbleArg,
                                        JSBoolean cancelableArg,
                                        Node relatedNodeArg,
                                        JSString prevValueArg,
                                        JSString newValueArg,
                                        JSString attrNameArg,
                                        JSNumber attrChangeArg) {
            callProcedure("initMutationEventNS", namespaceURIArg,
                                        typeArg,
                                        canBubbleArg,
                                        cancelableArg,
                                        relatedNodeArg,
                                        prevValueArg,
                                        newValueArg,
                                        attrNameArg,
                                        attrChangeArg);
        }
    }

    public class MutationNameEvent<T>: MutationEvent<T> where T: EventTarget {
        public JSString prevNamespaceURI {
            get {
                return getProperty<JSString>("prevNamespaceURI");
            }
        }

        public JSString prevNodeName {
            get {
                return getProperty<JSString>("prevNodeName");
            }
        }

        public void initMutationNameEvent(JSString typeArg,
                                          JSBoolean canBubbleArg,
                                          JSBoolean cancelableArg,
                                          Node relatedNodeArg,
                                          JSString prevNamespaceURIArg,
                                          JSString prevNodeNameArg) {
            callProcedure("initMutationNameEvent", typeArg,
                                          canBubbleArg,
                                          cancelableArg,
                                          relatedNodeArg,
                                          prevNamespaceURIArg,
                                          prevNodeNameArg);
        }

        public void initMutationNameEventNS(JSString namespaceURIArg,
                                            JSString typeArg,
                                            JSBoolean canBubbleArg,
                                            JSBoolean cancelableArg,
                                            Node relatedNodeArg,
                                            JSString prevNamespaceURIArg,
                                            JSString prevNodeNameArg) {
            callProcedure("initMutationNameEventNS", namespaceURIArg,
                                            typeArg,
                                            canBubbleArg,
                                            cancelableArg,
                                            relatedNodeArg,
                                            prevNamespaceURIArg,
                                            prevNodeNameArg);
        }
    }

    public class HtmlEventHandler<T> {
        protected T theHandler;
        protected bool toUseCapture;
        protected int theAction;

        public HtmlEventHandler() {
        }

        public HtmlEventHandler(T handler) {
            theHandler = handler;
            toUseCapture = false;
        }

        public HtmlEventHandler(T handler, bool useCapture) {
            theHandler = handler;
            toUseCapture = useCapture;
        }

        public T handler {
            get {
                return theHandler;
            }
        }

        public bool useCapture {
            get {
                return toUseCapture;
            }
        }

        public int action {
            get {
                return theAction;
            }
        }
        public static HtmlEventHandler<T> operator +(HtmlEventHandler<T> src, HtmlEventHandler<T> src2) {
            src.theHandler = src2.theHandler;
            src.toUseCapture = src2.toUseCapture;
            src.theAction = 1;
            return src;
        }

        public static HtmlEventHandler<T> operator -(HtmlEventHandler<T> src, HtmlEventHandler<T> src2) {
            src.theHandler = src2.theHandler;
            src.toUseCapture = src2.toUseCapture;
            src.theAction = -1;
            return src;
        }
    }
}
